﻿//-------------------------------------------------------------------
//	Copyright (c) 2010-2014  Zhirnov Andrey
//	This file is part of the "UX Standard Lib" project.
//	See copyright notice in "UX_STL_Ext.h".
//-------------------------------------------------------------------

#include "CLProgram.h"
#include "CLKernel.h"

namespace UX_STL
{
namespace CLShell
{
	
/*
=================================================
	конструктор
=================================================
*/
	CLProgram::CLProgram():
		_pID(null), _pCallback(null), _bBuilded(false)
	{
	}
	
/*
=================================================
	деструктор
=================================================
*/
	CLProgram::~CLProgram()
	{
	}
	
/*
=================================================
	создать программу из исходников
=================================================
*/
	bool CLProgram::Create(const uni_array< const char *> &aSources)
	{
		CHECK_ERR( !aSources.Empty() );

		Destroy();

		cl_int	i_cl_err = 0;

		CL_CALL_R( _pID = clCreateProgramWithSource( CLDEVICE()->Context(), (cl_uint) aSources.Count(),
													(const char **)aSources.ptr(), null, &i_cl_err ) );
		return true;
	}
	

	bool CLProgram::Create(const char *pSource)
	{
		return Create( uni_array< const char *>( &pSource, (usize)1 ) );
	}
	

	bool CLProgram::Create(const uni_array< string > &aSources)
	{
		array<const char *>	a_source;

		FOR( i, aSources ) {
			a_source.PushBack( aSources[i].cstr() );
		}
		return Create( a_source );
	}
	
/*
=================================================
	создать программу из бинарника
=================================================
*/
	bool CLProgram::CreateBinary(const uni_array< uint8 > &aData)
	{
		CHECK_ERR( !aData.Empty() );

		Destroy();
		
		cl_int			i_cl_err = 0;
		size_t			u_size	 = (size_t)aData.Size();
		const uint8	  *	p_data	 = aData.ptr();
		cl_device_id	p_dev	 = CLDEVICE()->DeviceId();

		CL_CALL_R( _pID = clCreateProgramWithBinary( CLDEVICE()->Context(), 1, &p_dev, &u_size,
													(const unsigned char**)&p_data, null, &i_cl_err ) );
		return true;
	}
	
/*
=================================================
	удалить программу
=================================================
*/
	void CLProgram::Destroy()
	{
		_ReleaseRef();
		_pID  = null;
		_pCallback = null;
		_bBuilded  = false;
	}
	
/*
=================================================
	возвращает текст программы
=================================================
*/
	bool CLProgram::GetSource(OUT string &sSource) const
	{
		CHECK_ERR( _pID != null );

		cl_int	i_cl_err = 0;
		size_t	u_size	 = 0;

		CL_CALL_R( i_cl_err = clGetProgramInfo( _pID, CL_PROGRAM_SOURCE, 0, null, &u_size ) );
		
		sSource.Reserve( u_size + 2 );
		CL_CALL_R( i_cl_err = clGetProgramInfo( _pID, CL_PROGRAM_SOURCE, (size_t)sSource.Size(),
												(void*)sSource.cstr(), null ) );

		sSource.SetLength( u_size );
		return true;
	}

/*
=================================================
	возвращает скомпилированную программу
=================================================
*/
	bool CLProgram::GetBinary(OUT binary_array_t &aData) const
	{
		CHECK_ERR( _pID != null );
		
		cl_int	i_cl_err = 0;
		cl_uint	u_count	 = 0;
		size_t	u_size	 = 0;

		CL_CALL_R( i_cl_err = clGetProgramInfo( _pID, CL_PROGRAM_NUM_DEVICES, sizeof(cl_uint), &u_count, null ) );
		CHECK_ERR( u_count == 1 );	// only 1 device supported

		CL_CALL_R( i_cl_err = clGetProgramInfo( _pID, CL_PROGRAM_BINARY_SIZES, sizeof(size_t), &u_size, null ) );
		
		aData.Resize( u_size, false );
		CL_CALL_R( i_cl_err = clGetProgramInfo( _pID, CL_PROGRAM_BINARIES, (size_t)aData.Size(), aData.ptr(), null ) );
		
		return true;
	}
	
/*
=================================================
	возвращает строку настроек препроцессора
=================================================
*/
	bool CLProgram::GetBuildOptions(OUT string &sOptions) const
	{
		CHECK_ERR( _pID != null );

		cl_int	i_cl_err = 0;
		size_t	u_size	 = 0;

		CL_CALL_R( i_cl_err = clGetProgramInfo( _pID, CL_PROGRAM_BUILD_OPTIONS, 0, null, &u_size ) );
		
		sOptions.Reserve( u_size + 2 );
		CL_CALL_R( i_cl_err = clGetProgramInfo( _pID, CL_PROGRAM_BUILD_OPTIONS, (size_t)sOptions.Size(),
												(void*)sOptions.cstr(), null ) );
		sOptions.SetLength( u_size );
		return true;
	}
	
/*
=================================================
	возвращает лог компиляции программы
=================================================
*/
	bool CLProgram::GetBuildLog(INOUT string &sLog) const
	{
		CHECK_ERR( _pID != null );

		cl_int	i_cl_err = 0;
		usize	u_size	 = 0;

		CL_CALL_R( i_cl_err = clGetProgramBuildInfo( _pID, CLDEVICE()->DeviceId(),
													 CL_PROGRAM_BUILD_LOG, 0, null, &u_size ) );
		
		string	buf;
		buf.Reserve( u_size + 1 );

		CL_CALL_R( i_cl_err = clGetProgramBuildInfo( _pID, CLDEVICE()->DeviceId(),
													 CL_PROGRAM_BUILD_LOG, u_size,
													 buf.ptr(), null ) );
		buf.SetLength( u_size );

		sLog << uni_c_string( buf.ptr(), u_size );
		return true;
	}

/*
=================================================
	скомпилировать программу
=================================================
*/
	bool CLProgram::Build(cl_prog_options::type eOpt)
	{
		return Build( eOpt, uni_string() );
	}

	bool CLProgram::Build(cl_prog_options::type eOpt, uni_c_string sMacroses)
	{
		CHECK_ERR( _pID != null and !_bBuilded );

		string			s_options;
		cl_int			i_cl_err = 0;
		cl_device_id	p_dev	 = CLDEVICE()->DeviceId();
		POnProgramBuild	p_proc	 = _pCallback == null ? null : &_s_OnBuild;
		void		*	p_param	 = _pCallback == null ? null : this;
		bool			res;

		_s_GetOptions( eOpt, sMacroses, s_options );

		CL_CALL_B( i_cl_err = clBuildProgram( _pID, 1, &p_dev, s_options.cstr(), p_proc, p_param ), res );

		_bBuilded = res;
		return res;
	}
	
/*
=================================================
	собрать все настройки в одну строку
=================================================
*/
	void CLProgram::_s_GetOptions(cl_prog_options::type eOpt, uni_c_string sMacroses, string &sRes)
	{
		// Add Enum Optins //
		if ( EnumCmp( eOpt, cl_prog_options::MATH_SINGLE_PRECISION_CONST ) )
			sRes << " -cl-single-precision-constant";

		if ( EnumCmp( eOpt, cl_prog_options::MATH_DENORMS_ARE_ZERO ) )
			sRes << " -cl-denorms-are-zero";

		if ( EnumCmp( eOpt, cl_prog_options::OPT_DISABLE ) )
			sRes << " -cl-opt-disable";

		if ( EnumCmp( eOpt, cl_prog_options::OPT_STRICT_ALIASING ) )
			sRes << " -cl-strict-aliasing";

		if ( EnumCmp( eOpt, cl_prog_options::OPT_MAD_ENABLE ) )
			sRes << " -cl-mad-enable";

		if ( EnumCmp( eOpt, cl_prog_options::OPT_NO_SIGNED_ZEROS ) )
			sRes << " -cl-no-signed-zeros";

		if ( EnumCmp( eOpt, cl_prog_options::OPT_FAST_RELAXED_MATH ) )
			sRes << " -cl-fast-relaxed-math";
		else
		{
			if ( EnumCmp( eOpt, cl_prog_options::OPT_UNSAFE_MATH ) )
				sRes << " -cl-unsafe-math-optimizations";

			if ( EnumCmp( eOpt, cl_prog_options::OPT_FINITE_MATH_ONLY ) )
				sRes << " -cl-finite-math-only";
		}

		if ( EnumCmp( eOpt, cl_prog_options::NO_WARNING ) )
			sRes << " -w";

		if ( EnumCmp( eOpt, cl_prog_options::WARNING_AS_ERROR ) )
			sRes << " -Werror";

		if ( EnumCmp( eOpt, cl_prog_options::CL_1_1 ) )
			sRes << " -cl-std=CL1.1";
		else
		if ( EnumCmp( eOpt, cl_prog_options::CL_1_0 ) )
			sRes << " -cl-std=CL1.0";

		sRes << sMacroses;
	}
	
/*
=================================================
	создать все kernel'ы программы
=================================================
*/
	bool CLProgram::CreateKernels(array<CLKernel *> &aKernels) const
	{
		CHECK_ERR( _pID != null and _bBuilded );

		cl_int	i_cl_err = 0;
		cl_uint	u_count	 = 0;

		CL_CALL_R( i_cl_err = clCreateKernelsInProgram( _pID, 0, null, &u_count ) );
		
		array<cl_kernel>	a_kernels( u_count, 0 );

		CL_CALL_R( i_cl_err = clCreateKernelsInProgram( _pID, (cl_uint)a_kernels.Count(), a_kernels.ptr(), null ) );
		
		for (usize i = 0; i < a_kernels.Count(); ++i)
		{
			CLKernel	*p_kernel = new CLKernel();

			if ( p_kernel->Create( const_cast<CLProgram *>(this), a_kernels[i] ) )
				aKernels.PushBack( p_kernel );
			else
			{
				delete p_kernel;
				clReleaseKernel( a_kernels[i] );
			}
		}

		return true;
	}

/*
=================================================
	установить объект вызываемый после компиляции программы
=================================================
*/
	void CLProgram::SetCallback(IProgramCallback *pCB)
	{
		_pCallback = pCB;
	}
	
/*
=================================================
	вызывает событие после компиляции программы
=================================================
*/
	void CL_CALLBACK CLProgram::_s_OnBuild(cl_program program, void *user_data)
	{
		CLProgram	*prog = (CLProgram *)user_data;

		if ( program == prog->_pID and prog->_pCallback != null )
			(*prog->_pCallback)( prog );
	}

	__UX_CL_RC_IMPL( CLProgram, _pID, Program, PROGRAM )

//-------------------------------------------------------------------

}	// CLShell
}	// UX_STL